Load Library Package

“Use the Tidyverse, Luke” – O-W.Kenobi

library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
-- Attaching packages --------------------------------------- tidyverse 1.2.1 --
v ggplot2 3.2.0     v purrr   0.3.2
v tibble  2.1.3     v dplyr   0.8.3
v tidyr   0.8.3     v stringr 1.4.0
v readr   1.3.1     v forcats 0.4.0
package 㤼㸱dplyr㤼㸲 was built under R version 3.6.1-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(skimr)

Attaching package: 㤼㸱skimr㤼㸲

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter
library(plotly)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: 㤼㸱plotly㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    last_plot

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter

The following object is masked from 㤼㸱package:graphics㤼㸲:

    layout

Get Data

Crossref data used from the Setup to the LC OpenRefine Workshop

crossref_data <- read_csv("https://raw.githubusercontent.com/LibraryCarpentry/lc-open-refine/gh-pages/data/doaj-article-sample.csv", 
    col_types = cols(Date = col_date(format = "%m/%d/%Y")))

Take a quick look at the data

glimpse(crossref_data)
Observations: 1,001
Variables: 11
$ Title     <chr> "The Fisher Thermodynamics of Quasi-Probabilities", "Aflatoxin Contamination of the...
$ Authors   <chr> "Flavia Pennini|Angelo Plastino", "Naveed Aslam|Peter C. Wynn", "Rafael R. C. Cuadr...
$ DOI       <chr> "10.3390/e17127853", "10.3390/agriculture5041172", "10.3390/ijms161226101", "10.339...
$ URL       <chr> "https://doaj.org/article/b75e8d5cca3f46cbbd63e91be5b32412", "https://doaj.org/arti...
$ Date      <date> 2015-01-11, 2015-01-11, 2015-01-11, 2015-01-11, 2015-01-11, 2015-01-11, 2015-01-11...
$ Language  <chr> "English", "English", "English", "EN", "EN", "English", "English", "English", "Engl...
$ Subjects  <chr> "Fisher information|quasi-probabilities|complementarity|Physics|QC1-999|Science|Q",...
$ ISSNs     <chr> "1099-4300", "2077-0472", "1422-0067", "2304-6740", "2306-5338", "1420-3049", "2073...
$ Publisher <chr> "MDPI AG", "MDPI AG", "MDPI AG", "MDPI AG", "MDPI AG", "MDPI AG", "MDPI AG", "MDPI ...
$ Citation  <chr> "Entropy, Vol 17, Iss 12, Pp 7848-7858 (2015)", "Agriculture (Basel), Vol 5, Iss 4,...
$ Licence   <chr> "CC BY", "CC BY", "CC BY", "CC BY", "CC BY", "CC BY", "CC BY", "CC BY", "CC BY", "C...
crossref_data

skimr

Skimr is a easy way to have a quick look at the variables in the data frame. In this case the data are mostly character string data. With numeric data skimr will produce a thumbnail histogram (sparkline )

skim(crossref_data)
Skim summary statistics
 n obs: 1001 
 n variables: 11 

-- Variable type:character -----------------------------------------------------
  variable missing complete    n min max empty n_unique
   Authors       0     1001 1001   7 291     0      883
  Citation       0     1001 1001  39 104     0     1000
       DOI      23      978 1001  16  29     0      977
     ISSNs       0     1001 1001   9  19     0       51
  Language      15      986 1001   2   7     0        4
   Licence       6      995 1001   5  11     0        3
 Publisher       0     1001 1001   7  47     0        6
  Subjects       0     1001 1001  17 337     0      988
     Title       0     1001 1001  18 318     0     1000
       URL       0     1001 1001  57  57     0     1000

-- Variable type:Date ----------------------------------------------------------
 variable missing complete    n        min        max     median n_unique
     Date       0     1001 1001 2015-01-01 2015-01-12 2015-01-07       12

Subsetting

aka “faceting” in OpenRefine speak.

Two methods to generate a quick table of the languages represented in the dataframe: count() and forcats::fct_count. Since these data are primarily character, it’s helpful to learn about factor data and the forcats package. These two tables are the same. It looks like the data are published in English (spelled two different ways), FRench and Spanish.

crossref_data %>% 
  count(Language)

fct_count(crossref_data$Language, sort = TRUE)

This time, subset on the governing license. All but six articles are covered by a createive commons license.

crossref_data %>% 
  count(Licence)

Subset on the publisher. Sort in descending order.

crossref_data %>% 
  count(Publisher, sort = TRUE)

Subset by authors, and sort by the most prolific. This field appears to be a multi-valued field that is pipe | separated. How do we count and visualize how many articles have multiple authors?

crossref_data %>% 
  count(Authors, sort = TRUE) 

The above table is not very useful (unless tracking publishing teams that are always expressed identically.) Let’s exploring some methods to generate a count of the pipe character separating each author in a single author field. The stringr::str_count() function is a great way to calculate the number of delimiters in each author field.

Note that counting a pipe character | requires using a Regular Expression, or regex. Anyone manipulating string characters with computers will be far more capable after spending some time learning about regular expressions. In this case the we’re looking for a pipe character |. The special trick, here, in understanding regex is to know that a pipe character has special meaning. Therefore we have to escape, or make it know that we want the literal pipe character and not the special meaning pipe character. To escape a character in regex one uses a backslash \. But the weird part is that, in R, one has to escape the the escape character: \\| means look for a literal |.

Below we count the number of pipe characters in each row of the Author field. Using the head function we only display the first six values (rows) in the Author column.

str_count(crossref_data$Authors, "\\|") %>% head()
[1] 1 1 2 3 2 3

Transform Data

Use dplyr::mutate to generate a new field that calculates how many authors each observation contains.

crossref_data %>% 
  select(Authors) %>% 
  mutate(multi_authorship = str_count(Authors, "\\|") + 1) %>% 
  select(Authors, multi_authorship)

Visualize

Authors

Generate a histogram distribution of the multiple authorship variable.

crossref_data %>% 
  select(Authors) %>% 
  mutate(multi_authorship = str_count(Authors, "\\|") + 1) %>% 
  select(multi_authorship, Authors) %>% 
  ggplot() +
  aes(multi_authorship) +
  geom_histogram(binwidth = 1)

This time generate as a bar graph and sort by the most frequent representation. Articles with five authors is the most frequent representation in the dataset.

auth_count <- crossref_data %>% 
  select(Authors) %>% 
  mutate(multi_authorship = str_count(Authors, "\\|") + 1) %>% 
  mutate(multi_authorship = as.character(multi_authorship)) %>% 
  select(multi_authorship, Authors)

ggplot(auth_count) +
  aes(fct_infreq(multi_authorship)) +
  geom_bar() +
  ggtitle("Multiple Authorship")

Explore Subject Headings

Visualize the frequency of multiple subject headings, just as with authors (A bar graph and a histogram)

crossref_data %>% 
  mutate(SH_count = str_count(Subjects, "\\|") + 1) %>% 
  mutate(SH_count = as.character(SH_count)) %>% 
  ggplot() +
  aes(fct_infreq(SH_count)) +
  geom_bar()


crossref_data %>% 
  mutate(SH_count = str_count(Subjects, "\\|") + 1) %>% 
  ggplot() +
  aes(SH_count) +
  geom_histogram(binwidth = 1) 

Data Transformations

Using dplyr, mutate a new variable and transform the data so that ‘EN’ and ‘English’ are the same. Transform ‘ES’ to “Spanish”, and ‘FR’ to “French”.

dplyr::case_when() is one specialized way to perform an if_else transformation.

crossref_data %>% 
  count(Language)

Since EN and English are synonymous, let’s combine them into a single value. case_when is a great function for collapsing values.

crossref_data <- crossref_data %>% 
  mutate(Language = case_when(
    Language == "EN" ~ "English",
    Language == "ES" ~ "Spanish",
    Language == "FR" ~ "French"
  )) 

Visualize the Languages.

Stacked Bar graph shows frequency by Language. Each stack of a bar distinguishes the publishers. English Language is huge and somewhat over-powers the reset of the graph. Make a second graph (below) to drill down on the lesser represented languages.

published_languages_bargraph <- crossref_data %>% 
  ggplot() +
  aes(fct_infreq(Language), fill = Publisher) +
  geom_bar()

published_languages_bargraph

Filter the data to show only the “NA”, “French”, and “Spanish”.

crossref_data %>% 
  filter(is.na(Language) | Language == "French" | Language == "Spanish") %>% 
  ggplot() +
  aes(fct_infreq(Language), fill = Publisher) +
  geom_bar() +
  labs(title = "Published Languages",
       subtitle = "NA or Non-English",
       caption = "Data Source: Crossref.org")

Time Series

published_over_time <- crossref_data %>% 
  count(Date) %>% 
  ggplot(aes(Date, n)) +
  geom_point() +
  geom_line() +
  labs("Publishing Frequency by Day", 
       subtitle = "January, 2015")
  
published_over_time

Interactive

Using Plottly’s ggplotly function, generate visualizations that are available for interactive mousing (i.e. subsetting and exploring). Gadgets such as sliders, drop-down menus, selection boxes and radio buttons are available and especially useful when combining library(crosstalk) with library(flexdashboards) as seen in the opening tab of this demonstration dashboard

ggplotly(published_languages_bargraph)
ggplotly(published_over_time)
LS0tDQp0aXRsZTogImZpcnN0IGxvb2sgYXQgdGhlIGRhdGFzZXQiDQpvdXRwdXQ6DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQotIGxpYnJhcnkgZGF0YQ0KLSBhdXRvbWF0aW9uDQoNCiMjIExvYWQgTGlicmFyeSBQYWNrYWdlDQoNCiJVc2UgdGhlIFtUaWR5dmVyc2VdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKSwgTHVrZSIgLS0gTy1XLktlbm9iaSANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2tpbXIpDQpsaWJyYXJ5KHBsb3RseSkNCmBgYA0KDQojIyBHZXQgRGF0YQ0KDQpDcm9zc3JlZiBkYXRhIHVzZWQgZnJvbSB0aGUgKipTZXR1cCoqIHRvIHRoZSBbTEMgT3BlblJlZmluZSBXb3Jrc2hvcF0oaHR0cHM6Ly9saWJyYXJ5Y2FycGVudHJ5Lm9yZy9sYy1vcGVuLXJlZmluZS9zZXR1cC5odG1sKQ0KDQpgYGB7cn0NCmNyb3NzcmVmX2RhdGEgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9MaWJyYXJ5Q2FycGVudHJ5L2xjLW9wZW4tcmVmaW5lL2doLXBhZ2VzL2RhdGEvZG9hai1hcnRpY2xlLXNhbXBsZS5jc3YiLCANCiAgICBjb2xfdHlwZXMgPSBjb2xzKERhdGUgPSBjb2xfZGF0ZShmb3JtYXQgPSAiJW0vJWQvJVkiKSkpDQpgYGANClRha2UgYSBxdWljayBsb29rIGF0IHRoZSBkYXRhDQoNCmBgYHtyfQ0KZ2xpbXBzZShjcm9zc3JlZl9kYXRhKQ0KY3Jvc3NyZWZfZGF0YQ0KYGBgDQoNCiMjIHNraW1yDQoNClNraW1yIGlzIGEgZWFzeSB3YXkgdG8gaGF2ZSBhIHF1aWNrIGxvb2sgYXQgdGhlIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBmcmFtZS4gIEluIHRoaXMgY2FzZSB0aGUgZGF0YSBhcmUgbW9zdGx5IGNoYXJhY3RlciBzdHJpbmcgZGF0YS4gIFdpdGggbnVtZXJpYyBkYXRhIHNraW1yIHdpbGwgcHJvZHVjZSBhIHRodW1ibmFpbCBoaXN0b2dyYW0gKHNwYXJrbGluZSApDQoNCmBgYHtyfQ0Kc2tpbShjcm9zc3JlZl9kYXRhKQ0KYGBgDQoNCg0KIyMgU3Vic2V0dGluZw0KDQpha2EgImZhY2V0aW5nIiBpbiBPcGVuUmVmaW5lIHNwZWFrLg0KDQpUd28gbWV0aG9kcyB0byBnZW5lcmF0ZSBhIHF1aWNrIHRhYmxlIG9mIHRoZSBsYW5ndWFnZXMgcmVwcmVzZW50ZWQgaW4gdGhlIGRhdGFmcmFtZTogIGBjb3VudCgpYCBhbmQgYGZvcmNhdHM6OmZjdF9jb3VudGAuICBTaW5jZSB0aGVzZSBkYXRhIGFyZSBwcmltYXJpbHkgY2hhcmFjdGVyLCBpdCdzIGhlbHBmdWwgdG8gbGVhcm4gYWJvdXQgZmFjdG9yIGRhdGEgYW5kIHRoZSBmb3JjYXRzIHBhY2thZ2UuIFRoZXNlIHR3byB0YWJsZXMgYXJlIHRoZSBzYW1lLiAgSXQgbG9va3MgbGlrZSB0aGUgZGF0YSBhcmUgcHVibGlzaGVkIGluIEVuZ2xpc2ggKHNwZWxsZWQgdHdvIGRpZmZlcmVudCB3YXlzKSwgRlJlbmNoIGFuZCBTcGFuaXNoLg0KDQpgYGB7cn0NCmNyb3NzcmVmX2RhdGEgJT4lIA0KICBjb3VudChMYW5ndWFnZSkNCg0KZmN0X2NvdW50KGNyb3NzcmVmX2RhdGEkTGFuZ3VhZ2UsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNClRoaXMgdGltZSwgc3Vic2V0IG9uIHRoZSBnb3Zlcm5pbmcgbGljZW5zZS4gIEFsbCBidXQgc2l4IGFydGljbGVzIGFyZSBjb3ZlcmVkIGJ5IGEgW2NyZWF0ZWl2ZSBjb21tb25zXShodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvKSBsaWNlbnNlLg0KDQpgYGB7cn0NCmNyb3NzcmVmX2RhdGEgJT4lIA0KICBjb3VudChMaWNlbmNlKQ0KYGBgDQoNClN1YnNldCBvbiB0aGUgcHVibGlzaGVyLiAgU29ydCBpbiBkZXNjZW5kaW5nIG9yZGVyLg0KDQpgYGB7cn0NCmNyb3NzcmVmX2RhdGEgJT4lIA0KICBjb3VudChQdWJsaXNoZXIsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNClN1YnNldCBieSBhdXRob3JzLCBhbmQgc29ydCBieSB0aGUgbW9zdCBwcm9saWZpYy4gIFRoaXMgZmllbGQgYXBwZWFycyB0byBiZSBhIG11bHRpLXZhbHVlZCBmaWVsZCB0aGF0IGlzIHBpcGUgYHxgIHNlcGFyYXRlZC4gIEhvdyBkbyB3ZSBjb3VudCBhbmQgdmlzdWFsaXplIGhvdyBtYW55IGFydGljbGVzIGhhdmUgbXVsdGlwbGUgYXV0aG9ycz8NCg0KYGBge3J9DQpjcm9zc3JlZl9kYXRhICU+JSANCiAgY291bnQoQXV0aG9ycywgc29ydCA9IFRSVUUpIA0KYGBgDQoNClRoZSBhYm92ZSB0YWJsZSBpcyBub3QgdmVyeSB1c2VmdWwgKHVubGVzcyB0cmFja2luZyBwdWJsaXNoaW5nIHRlYW1zIHRoYXQgYXJlIGFsd2F5cyBleHByZXNzZWQgaWRlbnRpY2FsbHkuKSAgTGV0J3MgZXhwbG9yaW5nIHNvbWUgbWV0aG9kcyB0byBnZW5lcmF0ZSBhIGNvdW50IG9mIHRoZSBwaXBlIGNoYXJhY3RlciBzZXBhcmF0aW5nIGVhY2ggYXV0aG9yIGluIGEgc2luZ2xlIGF1dGhvciBmaWVsZC4gIFRoZSBgc3RyaW5ncjo6c3RyX2NvdW50KClgIGZ1bmN0aW9uIGlzIGEgZ3JlYXQgd2F5IHRvIGNhbGN1bGF0ZSB0aGUgbnVtYmVyIG9mIGRlbGltaXRlcnMgaW4gZWFjaCBhdXRob3IgZmllbGQuICANCg0KTm90ZSB0aGF0IGNvdW50aW5nIGEgcGlwZSBjaGFyYWN0ZXIgYHxgIHJlcXVpcmVzIHVzaW5nIGEgUmVndWxhciBFeHByZXNzaW9uLCBvciBbcmVnZXhdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1JlZ3VsYXJfZXhwcmVzc2lvbikuICBBbnlvbmUgbWFuaXB1bGF0aW5nIHN0cmluZyBjaGFyYWN0ZXJzIHdpdGggY29tcHV0ZXJzIHdpbGwgYmUgZmFyIG1vcmUgY2FwYWJsZSBhZnRlciBzcGVuZGluZyBzb21lIHRpbWUgbGVhcm5pbmcgYWJvdXQgcmVndWxhciBleHByZXNzaW9ucy4gIEluIHRoaXMgY2FzZSB0aGUgd2UncmUgbG9va2luZyBmb3IgYSBwaXBlIGNoYXJhY3RlciBgfGAuICBUaGUgc3BlY2lhbCB0cmljaywgaGVyZSwgaW4gdW5kZXJzdGFuZGluZyByZWdleCBpcyB0byBrbm93IHRoYXQgYSBwaXBlIGNoYXJhY3RlciBoYXMgc3BlY2lhbCBtZWFuaW5nLiAgVGhlcmVmb3JlIHdlIGhhdmUgdG8gZXNjYXBlLCBvciBtYWtlIGl0IGtub3cgdGhhdCB3ZSB3YW50IHRoZSBsaXRlcmFsIHBpcGUgY2hhcmFjdGVyIGFuZCBub3QgdGhlIHNwZWNpYWwgbWVhbmluZyBwaXBlIGNoYXJhY3Rlci4gICBUbyBlc2NhcGUgYSBjaGFyYWN0ZXIgaW4gcmVnZXggb25lIHVzZXMgYSBiYWNrc2xhc2ggYFxgLiAgQnV0IHRoZSB3ZWlyZCBwYXJ0IGlzIHRoYXQsIGluIFIsIG9uZSBoYXMgdG8gZXNjYXBlIHRoZSB0aGUgZXNjYXBlIGNoYXJhY3RlcjogIGBcXHxgIG1lYW5zIGxvb2sgZm9yIGEgbGl0ZXJhbCBgfGAuDQoNCkJlbG93IHdlIGNvdW50IHRoZSBudW1iZXIgb2YgcGlwZSBjaGFyYWN0ZXJzIGluIGVhY2ggcm93IG9mIHRoZSBBdXRob3IgZmllbGQuICBVc2luZyB0aGUgYGhlYWRgIGZ1bmN0aW9uIHdlIG9ubHkgZGlzcGxheSB0aGUgZmlyc3Qgc2l4IHZhbHVlcyAocm93cykgaW4gdGhlIEF1dGhvciBjb2x1bW4uDQoNCmBgYHtyfQ0Kc3RyX2NvdW50KGNyb3NzcmVmX2RhdGEkQXV0aG9ycywgIlxcfCIpICU+JSBoZWFkKCkNCmBgYA0KDQojIyBUcmFuc2Zvcm0gRGF0YQ0KDQpVc2UgYGRwbHlyOjptdXRhdGVgIHRvIGdlbmVyYXRlIGEgbmV3IGZpZWxkIHRoYXQgY2FsY3VsYXRlcyBob3cgbWFueSBhdXRob3JzIGVhY2ggb2JzZXJ2YXRpb24gY29udGFpbnMuDQoNCmBgYHtyfQ0KY3Jvc3NyZWZfZGF0YSAlPiUgDQogIHNlbGVjdChBdXRob3JzKSAlPiUgDQogIG11dGF0ZShtdWx0aV9hdXRob3JzaGlwID0gc3RyX2NvdW50KEF1dGhvcnMsICJcXHwiKSArIDEpICU+JSANCiAgc2VsZWN0KEF1dGhvcnMsIG11bHRpX2F1dGhvcnNoaXApDQpgYGANCg0KIyMgVmlzdWFsaXplDQoNCiMjIyBBdXRob3JzDQoNCkdlbmVyYXRlIGEgaGlzdG9ncmFtIGRpc3RyaWJ1dGlvbiBvZiB0aGUgbXVsdGlwbGUgYXV0aG9yc2hpcCB2YXJpYWJsZS4NCg0KYGBge3J9DQpjcm9zc3JlZl9kYXRhICU+JSANCiAgc2VsZWN0KEF1dGhvcnMpICU+JSANCiAgbXV0YXRlKG11bHRpX2F1dGhvcnNoaXAgPSBzdHJfY291bnQoQXV0aG9ycywgIlxcfCIpICsgMSkgJT4lIA0KICBzZWxlY3QobXVsdGlfYXV0aG9yc2hpcCwgQXV0aG9ycykgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGFlcyhtdWx0aV9hdXRob3JzaGlwKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkNCmBgYA0KDQpUaGlzIHRpbWUgZ2VuZXJhdGUgYXMgYSBiYXIgZ3JhcGggYW5kIHNvcnQgYnkgdGhlIG1vc3QgZnJlcXVlbnQgcmVwcmVzZW50YXRpb24uICBBcnRpY2xlcyB3aXRoIGZpdmUgYXV0aG9ycyBpcyB0aGUgbW9zdCBmcmVxdWVudCByZXByZXNlbnRhdGlvbiBpbiB0aGUgZGF0YXNldC4gDQoNCmBgYHtyfQ0KYXV0aF9jb3VudCA8LSBjcm9zc3JlZl9kYXRhICU+JSANCiAgc2VsZWN0KEF1dGhvcnMpICU+JSANCiAgbXV0YXRlKG11bHRpX2F1dGhvcnNoaXAgPSBzdHJfY291bnQoQXV0aG9ycywgIlxcfCIpICsgMSkgJT4lIA0KICBtdXRhdGUobXVsdGlfYXV0aG9yc2hpcCA9IGFzLmNoYXJhY3RlcihtdWx0aV9hdXRob3JzaGlwKSkgJT4lIA0KICBzZWxlY3QobXVsdGlfYXV0aG9yc2hpcCwgQXV0aG9ycykNCg0KZ2dwbG90KGF1dGhfY291bnQpICsNCiAgYWVzKGZjdF9pbmZyZXEobXVsdGlfYXV0aG9yc2hpcCkpICsNCiAgZ2VvbV9iYXIoKSArDQogIGdndGl0bGUoIk11bHRpcGxlIEF1dGhvcnNoaXAiKQ0KYGBgDQoNCiMjIyBFeHBsb3JlIFN1YmplY3QgSGVhZGluZ3MNCg0KVmlzdWFsaXplIHRoZSBmcmVxdWVuY3kgb2YgbXVsdGlwbGUgc3ViamVjdCBoZWFkaW5ncywganVzdCBhcyB3aXRoIGF1dGhvcnMgKEEgYmFyIGdyYXBoIGFuZCBhIGhpc3RvZ3JhbSkNCg0KYGBge3J9DQpjcm9zc3JlZl9kYXRhICU+JSANCiAgbXV0YXRlKFNIX2NvdW50ID0gc3RyX2NvdW50KFN1YmplY3RzLCAiXFx8IikgKyAxKSAlPiUgDQogIG11dGF0ZShTSF9jb3VudCA9IGFzLmNoYXJhY3RlcihTSF9jb3VudCkpICU+JSANCiAgZ2dwbG90KCkgKw0KICBhZXMoZmN0X2luZnJlcShTSF9jb3VudCkpICsNCiAgZ2VvbV9iYXIoKQ0KDQpjcm9zc3JlZl9kYXRhICU+JSANCiAgbXV0YXRlKFNIX2NvdW50ID0gc3RyX2NvdW50KFN1YmplY3RzLCAiXFx8IikgKyAxKSAlPiUgDQogIGdncGxvdCgpICsNCiAgYWVzKFNIX2NvdW50KSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSkgDQpgYGANCg0KDQojIyBEYXRhIFRyYW5zZm9ybWF0aW9ucw0KDQpVc2luZyBkcGx5ciwgbXV0YXRlIGEgbmV3IHZhcmlhYmxlIGFuZCB0cmFuc2Zvcm0gdGhlIGRhdGEgc28gdGhhdCAnRU4nIGFuZCAnRW5nbGlzaCcgYXJlIHRoZSBzYW1lLiAgVHJhbnNmb3JtICdFUycgdG8gIlNwYW5pc2giLCBhbmQgJ0ZSJyB0byAiRnJlbmNoIi4gIA0KDQpgZHBseXI6OmNhc2Vfd2hlbigpYCBpcyBvbmUgc3BlY2lhbGl6ZWQgd2F5IHRvIHBlcmZvcm0gYW4gYGlmX2Vsc2VgIHRyYW5zZm9ybWF0aW9uLg0KDQpgYGB7cn0NCmNyb3NzcmVmX2RhdGEgJT4lIA0KICBjb3VudChMYW5ndWFnZSkNCmBgYA0KDQpTaW5jZSBgRU5gIGFuZCBgRW5nbGlzaGAgYXJlIHN5bm9ueW1vdXMsIGxldCdzIGNvbWJpbmUgdGhlbSBpbnRvIGEgc2luZ2xlIHZhbHVlLiAgYGNhc2Vfd2hlbmAgaXMgYSBncmVhdCBmdW5jdGlvbiBmb3IgY29sbGFwc2luZyB2YWx1ZXMuDQoNCmBgYHtyfQ0KY3Jvc3NyZWZfZGF0YSA8LSBjcm9zc3JlZl9kYXRhICU+JSANCiAgbXV0YXRlKExhbmd1YWdlID0gY2FzZV93aGVuKA0KICAgIExhbmd1YWdlID09ICJFTiIgfiAiRW5nbGlzaCIsDQogICAgTGFuZ3VhZ2UgPT0gIkVTIiB+ICJTcGFuaXNoIiwNCiAgICBMYW5ndWFnZSA9PSAiRlIiIH4gIkZyZW5jaCINCiAgKSkgDQpgYGANCg0KIyMjIFZpc3VhbGl6ZSB0aGUgTGFuZ3VhZ2VzLiANCg0KU3RhY2tlZCBCYXIgZ3JhcGggc2hvd3MgZnJlcXVlbmN5IGJ5IExhbmd1YWdlLiBFYWNoIHN0YWNrIG9mIGEgYmFyIGRpc3Rpbmd1aXNoZXMgdGhlIHB1Ymxpc2hlcnMuIEVuZ2xpc2ggTGFuZ3VhZ2UgaXMgaHVnZSBhbmQgc29tZXdoYXQgb3Zlci1wb3dlcnMgdGhlIHJlc2V0IG9mIHRoZSBncmFwaC4gIE1ha2UgYSBzZWNvbmQgZ3JhcGggKGJlbG93KSB0byBkcmlsbCBkb3duIG9uIHRoZSBsZXNzZXIgcmVwcmVzZW50ZWQgbGFuZ3VhZ2VzLg0KDQpgYGB7cn0NCnB1Ymxpc2hlZF9sYW5ndWFnZXNfYmFyZ3JhcGggPC0gY3Jvc3NyZWZfZGF0YSAlPiUgDQogIGdncGxvdCgpICsNCiAgYWVzKGZjdF9pbmZyZXEoTGFuZ3VhZ2UpLCBmaWxsID0gUHVibGlzaGVyKSArDQogIGdlb21fYmFyKCkNCg0KcHVibGlzaGVkX2xhbmd1YWdlc19iYXJncmFwaA0KYGBgDQoNCkZpbHRlciB0aGUgZGF0YSB0byBzaG93IG9ubHkgdGhlICJOQSIsICJGcmVuY2giLCBhbmQgIlNwYW5pc2giLg0KDQpgYGB7cn0NCmNyb3NzcmVmX2RhdGEgJT4lIA0KICBmaWx0ZXIoaXMubmEoTGFuZ3VhZ2UpIHwgTGFuZ3VhZ2UgPT0gIkZyZW5jaCIgfCBMYW5ndWFnZSA9PSAiU3BhbmlzaCIpICU+JSANCiAgZ2dwbG90KCkgKw0KICBhZXMoZmN0X2luZnJlcShMYW5ndWFnZSksIGZpbGwgPSBQdWJsaXNoZXIpICsNCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAiUHVibGlzaGVkIExhbmd1YWdlcyIsDQogICAgICAgc3VidGl0bGUgPSAiTkEgb3IgTm9uLUVuZ2xpc2giLA0KICAgICAgIGNhcHRpb24gPSAiRGF0YSBTb3VyY2U6IENyb3NzcmVmLm9yZyIpDQpgYGANCg0KIyMgVGltZSBTZXJpZXMNCg0KYGBge3J9DQpwdWJsaXNoZWRfb3Zlcl90aW1lIDwtIGNyb3NzcmVmX2RhdGEgJT4lIA0KICBjb3VudChEYXRlKSAlPiUgDQogIGdncGxvdChhZXMoRGF0ZSwgbikpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBsYWJzKCJQdWJsaXNoaW5nIEZyZXF1ZW5jeSBieSBEYXkiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJKYW51YXJ5LCAyMDE1IikNCiAgDQpwdWJsaXNoZWRfb3Zlcl90aW1lDQpgYGANCiMjIEludGVyYWN0aXZlDQoNClVzaW5nIFBsb3R0bHkncyBgZ2dwbG90bHlgIGZ1bmN0aW9uLCBnZW5lcmF0ZSB2aXN1YWxpemF0aW9ucyB0aGF0IGFyZSBhdmFpbGFibGUgZm9yIGludGVyYWN0aXZlIG1vdXNpbmcgKGkuZS4gc3Vic2V0dGluZyBhbmQgZXhwbG9yaW5nKS4gIEdhZGdldHMgc3VjaCBhcyBzbGlkZXJzLCBkcm9wLWRvd24gbWVudXMsIHNlbGVjdGlvbiBib3hlcyBhbmQgcmFkaW8gYnV0dG9ucyBhcmUgYXZhaWxhYmxlIGFuZCBlc3BlY2lhbGx5IHVzZWZ1bCB3aGVuIGNvbWJpbmluZyBsaWJyYXJ5KGNyb3NzdGFsaykgd2l0aCBsaWJyYXJ5KGZsZXhkYXNoYm9hcmRzKSBbYXMgc2VlbiBpbiB0aGUgb3BlbmluZyB0YWIgb2YgdGhpcyBkZW1vbnN0cmF0aW9uIGRhc2hib2FyZF0oaHR0cHM6Ly9yZnVuLWZsZXhkYXNoYm9hcmRzLm5ldGxpZnkuY29tLykNCg0KDQpgYGB7cn0NCmdncGxvdGx5KHB1Ymxpc2hlZF9sYW5ndWFnZXNfYmFyZ3JhcGgpDQpgYGANCg0KDQpgYGB7cn0NCmdncGxvdGx5KHB1Ymxpc2hlZF9vdmVyX3RpbWUpDQpgYGANCg0K